package cn.iocoder.yudao.module.trade.service.drainage;

import cn.hutool.core.lang.UUID;
import cn.iocoder.yudao.module.infra.api.config.ConfigApi;
import cn.iocoder.yudao.module.infra.api.config.dto.ConfigDTO;
import cn.iocoder.yudao.module.trade.controller.admin.orderpostback.vo.OrderPostBackSaveReqVO;
import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
import cn.iocoder.yudao.module.trade.dal.dataobject.orderpostback.OrderPostBackDO;
import cn.iocoder.yudao.module.trade.dal.dataobject.orderpostconfig.OrderPostConfigDO;
import cn.iocoder.yudao.module.trade.enums.orderpostback.OrderPostBackPayTypeEnum;
import cn.iocoder.yudao.module.trade.enums.orderpostback.TradeOrderPostBackStatusEnum;
import cn.iocoder.yudao.module.trade.service.drainage.bo.VivoBehaviorUploadBO;
import cn.iocoder.yudao.module.trade.service.drainage.bo.VivoClientBO;
import cn.iocoder.yudao.module.trade.service.drainage.bo.VivoTokenBO;
import cn.iocoder.yudao.module.trade.service.orderpostback.OrderPostBackService;
import com.alibaba.fastjson.JSONObject;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.*;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.*;

/**
 * VIVO推广平台
 */
@Slf4j
@Service
public class VivoDrainageServiceImpl implements DrainageService {

    @Getter
    @Setter
    public Integer drainageType = 2; //VIVO推广类型
    @Value("${drainage.type.vivo.server:https://marketing-api.vivo.com.cn}")
    private String serverUrl;
    @Value("${drainage.type.vivo.redirect:https://www.zhangfengkj.com.cn/admin-api/trade/third-auth/vivo}")
    private String redirectUrl;
    @Value("${draginage.type.vivo.auth:https://open-ad.vivo.com.cn/OAuth?clientId=%s&state=%s&redirectUri=%s}")
    private String authUrl;
    private static final String getAccessTokenUrl = "/openapi/v1/oauth2/token";
    private static final String refreshTokenUrl = "/openapi/v1/oauth2/refreshToken";
    private static final String behaviorUpload = "/openapi/v1/advertiser/behavior/upload";

    private static final String DRAINAGE_TYPE_VIVO_ENABLE = "drainage.type.vivo.enable";
    private static final String DRAINAGE_TYPE_VIVO_CLIENT_ID = "drainage.type.vivo.clientId";
    private static final String DRAINAGE_TYPE_VIVO_SECRET = "drainage.type.vivo.secret";
    private static final String DRAINAGE_TYPE_VIVO_STATE = "drainage.type.vivo.state";
    private static final String DRAINAGE_TYPE_VIVO_SRC = "drainage.type.vivo.srcId";
    private static final String DRAINAGE_TYPE_VIVO_PAGE = "drainage.type.vivo.page";
    private static final String VIVO_CACHE_KEY = "drainage.type.vivo.token:%s";

    @Resource
    private StringRedisTemplate stringRedisTemplate;
    @Resource
    private ConfigApi configApi;
    @Resource
    private OrderPostBackService orderPostBackService;
    @Resource
    protected RestTemplate restTemplate;

    private VivoClientBO getCientInfo() {
        boolean enable = Optional.ofNullable(configApi.getConfigByKey(DRAINAGE_TYPE_VIVO_ENABLE))
                .map(ConfigDTO::getValue).map(Boolean::parseBoolean).orElse(false);
        String clientId = Optional.ofNullable(configApi.getConfigByKey(DRAINAGE_TYPE_VIVO_CLIENT_ID))
                .map(ConfigDTO::getValue).orElse(null);
        String state = Optional.ofNullable(configApi.getConfigByKey(DRAINAGE_TYPE_VIVO_STATE))
                .map(ConfigDTO::getValue).orElse(null);
        String secret = Optional.ofNullable(configApi.getConfigByKey(DRAINAGE_TYPE_VIVO_SECRET))
                .map(ConfigDTO::getValue).orElse(null);
        return new VivoClientBO(clientId, secret, state, enable);
    }

    private VivoClientBO getBusinessInfo() {
        VivoClientBO clientBO = getCientInfo();
        String src = Optional.ofNullable(configApi.getConfigByKey(DRAINAGE_TYPE_VIVO_SRC))
                .map(ConfigDTO::getValue).orElse(null);
        String page = Optional.ofNullable(configApi.getConfigByKey(DRAINAGE_TYPE_VIVO_PAGE))
                .map(ConfigDTO::getValue).orElse(null);
        clientBO.setSrcId(src);
        clientBO.setPageUrl(page);
        return clientBO;
    }

    @Override
    public boolean processOrder(TradeOrderDO orderDO, Map<String, String> param, OrderPostConfigDO configDo, boolean send) {
        return processOrderByOrderType(orderDO, param, configDo, send, OrderPostBackPayTypeEnum.PAY);
    }

    @Override
    public boolean processOrder(OrderPostBackDO orderPostBackDO) {
        return false;
    }

    private boolean processOrderByOrderType(TradeOrderDO orderDO, Map<String, String> param, OrderPostConfigDO configDo, boolean send, OrderPostBackPayTypeEnum orderType) {
        OrderPostBackSaveReqVO saveReqVO = createOrderPostBack(orderDO, String.format("%s_%s", param.get("requestId"), param.get("creativeId")), drainageType,
                orderDO.getPayPrice(), orderType.getType(), configDo, send ? TradeOrderPostBackStatusEnum.UN_SEND : TradeOrderPostBackStatusEnum.OFF_SHELF);
        if (send) {
            sendOrder(saveReqVO);
        }
        orderPostBackService.createOrderPostBack(saveReqVO);
        return true;
    }

    private void sendOrder(OrderPostBackSaveReqVO reqVO) {
        String type;
        VivoClientBO clientBO = getBusinessInfo();
        if (OrderPostBackPayTypeEnum.PAY.getType().equals(reqVO.getOrderType())) {
            // 用户支付
            type = "SUBMIT";
        } else if (OrderPostBackPayTypeEnum.UN_PAY.getType().equals(reqVO.getOrderType())) {
            // 用户激活
            type = "ACTIVATION";
        } else {
            log.error("VIVO未找到对应回传类型: {}", reqVO.getOrderType());
            return;
        }
        boolean success = false;
        if (clientBO.isEnable() && StringUtils.isNoneEmpty(reqVO.getThirdId())) {
            try {
                String[] thirds = reqVO.getThirdId().split("_");
                if (thirds.length == 2) {
                    VivoBehaviorUploadBO behaviorUploadBO = new VivoBehaviorUploadBO();
                    behaviorUploadBO.setPageUrl(clientBO.getPageUrl());
                    behaviorUploadBO.setSrcId(clientBO.getSrcId());
                    behaviorUploadBO.setSrcType("Web");
                    VivoBehaviorUploadBO.Item item = new VivoBehaviorUploadBO.Item();
                    item.setRequestId(thirds[0]);
                    item.setCreativeId(thirds[1]);
                    item.setCvType(type);
                    item.setCvTime(System.currentTimeMillis());
                    Map<String, String> param = new HashMap<>();
                    param.put("payAmount", String.valueOf(reqVO.getPayPrice()));
                    item.setExtParam(param);
                    behaviorUploadBO.setDataList(Collections.singletonList(item));
                    String accessToken = getToken();
                    if (StringUtils.isNoneEmpty(accessToken)) {
                        String url = serverUrl + behaviorUpload + "?access_token=" + accessToken +
                                "&timestamp=" + System.currentTimeMillis() + "&nonce=" + getUuid() + "&advertiser_id=" + clientBO.getSecret();
                        HttpHeaders headers = new HttpHeaders();
                        headers.setContentType(MediaType.APPLICATION_JSON);
                        HttpEntity<String> entity = new HttpEntity<>(JSONObject.toJSONString(behaviorUploadBO), headers);
                        ResponseEntity<String> body = restTemplate.postForEntity(url, entity, String.class);
                        if (body.getStatusCode().is2xxSuccessful()) {
                            JSONObject jsonObject = JSONObject.parseObject(body.getBody());
                            success = jsonObject.getIntValue("code") == 0;
                            if (!success) {
                                log.error("数据回传失败:code: {}, message: {}", jsonObject.getIntValue("code"), jsonObject.getString("message"));
                            }

                        }
                    }
                }
            } catch (Exception e) {
                log.error("请求发送失败, error : {}", e.getMessage());
            } finally {
                reqVO.setStatus(success ?
                        TradeOrderPostBackStatusEnum.FINISH.getStatus() :
                        TradeOrderPostBackStatusEnum.FAILED.getStatus());
                reqVO.setPostTime(LocalDateTime.now());
            }
        }
    }

    @Override
    public boolean unPayOrderPost(TradeOrderDO tradeOrderDO, Map<String, String> param, OrderPostConfigDO configDO) {
        return processOrderByOrderType(tradeOrderDO, param, configDO, true, OrderPostBackPayTypeEnum.UN_PAY);
    }

    private String getUuid() {
        return UUID.randomUUID().toString().replaceAll("-", "");
    }

    public String cacheAccessToken() {
        VivoClientBO clientInfo = getCientInfo();
        return String.format(authUrl,
                clientInfo.getClientId(), clientInfo.getState(), redirectUrl);
    }

    public String cacheToken(String code) {
        VivoClientBO clientBO = getCientInfo();
        String url = serverUrl + getAccessTokenUrl + "?client_id=" + clientBO.getClientId()
                + "&client_secret=" + clientBO.getSecret()
                + "&grant_type=code&code=" + code;
        ResponseEntity<String> result = restTemplate.getForEntity(url, String.class);
        if (result.getStatusCode().is2xxSuccessful()) {
            JSONObject body = JSONObject.parseObject(result.getBody());
            if (body.containsKey("code") && body.getIntValue("code") == 0) {
                VivoTokenBO vivoTokenBO = body.getJSONObject("data").toJavaObject(VivoTokenBO.class);
                stringRedisTemplate.opsForValue().set(String.format(VIVO_CACHE_KEY, clientBO.getClientId()), JSONObject.toJSONString(vivoTokenBO));
                return vivoTokenBO.getAccessToken();
            }
        }
        return null;
    }

    public String getToken() {
        VivoClientBO clientBO = getCientInfo();
        if (clientBO.isEnable() && clientBO.isAvailable()) {
            String value = stringRedisTemplate.opsForValue().get(String.format(VIVO_CACHE_KEY, clientBO.getClientId()));
            if (StringUtils.isNoneEmpty(value)) {
                VivoTokenBO tokenBO = JSONObject.parseObject(value, VivoTokenBO.class);
                assert tokenBO != null;
                return getToken(tokenBO);
            }
        }
        return null;
    }

    private String getToken(VivoTokenBO tokenBO) {
        long currentTime = System.currentTimeMillis();
        if (currentTime < tokenBO.getTokenDate()) {
            //小于两小时， 重新刷新token
            if (tokenBO.getTokenDate() - currentTime < 7200 * 1000) {
                return refreshToken(tokenBO);
            }
            return tokenBO.getAccessToken();
        }
        if (currentTime < tokenBO.getRefreshTokenDate()) {
            return refreshToken(tokenBO);
        }
        return null;
    }

    private String refreshToken(VivoTokenBO tokenBo) {
        VivoClientBO clientBO = getCientInfo();
        String url = serverUrl + refreshTokenUrl + "?client_id=" + clientBO.getClientId()
                + "&client_secret=" + clientBO.getSecret()
                + "&refresh_token=" + tokenBo.getRefreshToken();
        ResponseEntity<String> result = restTemplate.getForEntity(url, String.class);
        if (result.getStatusCode().is2xxSuccessful()) {
            JSONObject body = JSONObject.parseObject(result.getBody());
            if (body.containsKey("code") && body.getIntValue("code") == 0) {
                VivoTokenBO vivoTokenBO = body.getJSONObject("data").toJavaObject(VivoTokenBO.class);
                stringRedisTemplate.opsForValue().set(String.format(VIVO_CACHE_KEY, clientBO.getClientId()), JSONObject.toJSONString(vivoTokenBO));
                return vivoTokenBO.getAccessToken();
            }
        }
        return null;
    }

}
